package net.wicp.tams.common.binlog.parser;

import net.wicp.tams.common.binlog.parser.event.LogHeader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Binary log event definitions. This includes generic code common to all types
 * of log events, as well as specific code for each type of log event. - All
 * numbers, whether they are 16-, 24-, 32-, or 64-bit numbers, are stored in
 * little endian, i.e., the least significant byte first, unless otherwise
 * specified. representation of unsigned integers, called Packed Integer. A
 * Packed Integer has the capacity of storing up to 8-byte integers, while small
 * integers still can use 1, 3, or 4 bytes. The value of the first byte
 * determines how to read the number, according to the following table:
 * <table>
 * <caption>Format of Packed Integer</caption>
 * <tr>
 * <th>First byte</th>
 * <th>Format</th>
 * </tr>
 * <tr>
 * <td>0-250</td>
 * <td>The first byte is the number (in the range 0-250), and no more bytes are
 * used.</td>
 * </tr>
 * <tr>
 * <td>252</td>
 * <td>Two more bytes are used. The number is in the range 251-0xffff.</td>
 * </tr>
 * <tr>
 * <td>253</td>
 * <td>Three more bytes are used. The number is in the range
 * 0xffff-0xffffff.</td>
 * </tr>
 * <tr>
 * <td>254</td>
 * <td>Eight more bytes are used. The number is in the range
 * 0xffffff-0xffffffffffffffff.</td>
 * </tr>
 * </table>
 * - Strings are stored in various formats. The format of each string is
 * documented separately.
 * 
 */
public abstract class LogEvent {

	/*
	 * 3 is MySQL 4.x; 4 is MySQL 5.0.0. Compared to version 3, version 4 has: -
	 * a different Start_log_event, which includes info about the binary log
	 * (sizes of headers); this info is included for better compatibility if the
	 * master's MySQL version is different from the slave's. - all events have a
	 * unique ID (the triplet (server_id, timestamp at server start, other) to
	 * be sure an event is not executed more than once in a multimaster setup,
	 * example: M1 / \ v v M2 M3 \ / v v S if a query is run on M1, it will
	 * arrive twice on S, so we need that S remembers the last unique ID it has
	 * processed, to compare and know if the event should be skipped or not.
	 * Example of ID: we already have the server id (4 bytes), plus:
	 * timestamp_when_the_master_started (4 bytes), a counter (a sequence number
	 * which increments every time we write an event to the binlog) (3 bytes).
	 * Q: how do we handle when the counter is overflowed and restarts from 0 ?
	 * - Query and Load (Create or Execute) events may have a more precise
	 * timestamp (with microseconds), number of matched/affected/warnings rows
	 * and fields of session variables: SQL_MODE, FOREIGN_KEY_CHECKS,
	 * UNIQUE_CHECKS, SQL_AUTO_IS_NULL, the collations and charsets, the
	 * PASSWORD() version (old/new/...).
	 */
	public static final int BINLOG_VERSION = 4;

	/* Default 5.0 server version */
	public static final String SERVER_VERSION = "5.0";

	/**
	 * Event header offsets; these point to places inside the fixed header.
	 */
	public static final int EVENT_TYPE_OFFSET = 4;
	public static final int SERVER_ID_OFFSET = 5;
	public static final int EVENT_LEN_OFFSET = 9;
	public static final int LOG_POS_OFFSET = 13;
	public static final int FLAGS_OFFSET = 17;

	/* event-specific post-header sizes */
	// where 3.23, 4.x and 5.0 agree
	public static final int QUERY_HEADER_MINIMAL_LEN = (4 + 4 + 1 + 2);
	// where 5.0 differs: 2 for len of N-bytes vars.
	public static final int QUERY_HEADER_LEN = (QUERY_HEADER_MINIMAL_LEN + 2);

	/* Enumeration type for the different types of log events. */
	public static final int UNKNOWN_EVENT = 0;
	public static final int START_EVENT_V3 = 1;
	public static final int QUERY_EVENT = 2;
	public static final int STOP_EVENT = 3;
	public static final int ROTATE_EVENT = 4;
	public static final int INTVAR_EVENT = 5;
	public static final int LOAD_EVENT = 6;
	public static final int SLAVE_EVENT = 7;
	public static final int CREATE_FILE_EVENT = 8;
	public static final int APPEND_BLOCK_EVENT = 9;
	public static final int EXEC_LOAD_EVENT = 10;
	public static final int DELETE_FILE_EVENT = 11;

	/**
	 * NEW_LOAD_EVENT is like LOAD_EVENT except that it has a longer sql_ex,
	 * allowing multibyte TERMINATED BY etc; both types share the same class
	 * (Load_log_event)
	 */
	public static final int NEW_LOAD_EVENT = 12;
	public static final int RAND_EVENT = 13;
	public static final int USER_VAR_EVENT = 14;
	public static final int FORMAT_DESCRIPTION_EVENT = 15;
	public static final int XID_EVENT = 16;
	public static final int BEGIN_LOAD_QUERY_EVENT = 17;
	public static final int EXECUTE_LOAD_QUERY_EVENT = 18;

	public static final int TABLE_MAP_EVENT = 19;

	/**
	 * These event numbers were used for 5.1.0 to 5.1.15 and are therefore
	 * obsolete.
	 */
	public static final int PRE_GA_WRITE_ROWS_EVENT = 20;
	public static final int PRE_GA_UPDATE_ROWS_EVENT = 21;
	public static final int PRE_GA_DELETE_ROWS_EVENT = 22;

	/**
	 * These event numbers are used from 5.1.16 and forward
	 */
	public static final int WRITE_ROWS_EVENT_V1 = 23;
	public static final int UPDATE_ROWS_EVENT_V1 = 24;
	public static final int DELETE_ROWS_EVENT_V1 = 25;

	/**
	 * Something out of the ordinary happened on the master
	 */
	public static final int INCIDENT_EVENT = 26;

	/**
	 * Heartbeat event to be send by master at its idle time to ensure master's
	 * online status to slave
	 */
	public static final int HEARTBEAT_LOG_EVENT = 27;

	/**
	 * In some situations, it is necessary to send over ignorable data to the
	 * slave: data that a slave can handle in case there is code for handling
	 * it, but which can be ignored if it is not recognized.
	 */
	public static final int IGNORABLE_LOG_EVENT = 28;
	public static final int ROWS_QUERY_LOG_EVENT = 29;

	/** Version 2 of the Row events */
	public static final int WRITE_ROWS_EVENT = 30;
	public static final int UPDATE_ROWS_EVENT = 31;
	public static final int DELETE_ROWS_EVENT = 32;

	public static final int GTID_LOG_EVENT = 33;
	public static final int ANONYMOUS_GTID_LOG_EVENT = 34;

	public static final int PREVIOUS_GTIDS_LOG_EVENT = 35;

	// mariaDb 5.5.34
	/* New MySQL/Sun events are to be added right above this comment */
	public static final int MYSQL_EVENTS_END = 36;

	public static final int MARIA_EVENTS_BEGIN = 160;
	/* New Maria event numbers start from here */
	public static final int ANNOTATE_ROWS_EVENT = 160;
	/*
	 * Binlog checkpoint event. Used for XA crash recovery on the master, not
	 * used in replication. A binlog checkpoint event specifies a binlog file
	 * such that XA crash recovery can start from that file - and it is
	 * guaranteed to find all XIDs that are prepared in storage engines but not
	 * yet committed.
	 */
	public static final int BINLOG_CHECKPOINT_EVENT = 161;
	/*
	 * Gtid event. For global transaction ID, used to start a new event group,
	 * instead of the old BEGIN query event, and also to mark stand-alone
	 * events.
	 */
	public static final int GTID_EVENT = 162;
	/*
	 * Gtid list event. Logged at the start of every binlog, to record the
	 * current replication state. This consists of the last GTID seen for each
	 * replication domain.
	 */
	public static final int GTID_LIST_EVENT = 163;

	/** end marker */
	public static final int ENUM_END_EVENT = 164;

	/**
	 * 1 byte length, 1 byte format Length is total length in bytes, including 2
	 * byte header Length values 0 and 1 are currently invalid and reserved.
	 */
	public static final int EXTRA_ROW_INFO_LEN_OFFSET = 0;
	public static final int EXTRA_ROW_INFO_FORMAT_OFFSET = 1;
	public static final int EXTRA_ROW_INFO_HDR_BYTES = 2;
	public static final int EXTRA_ROW_INFO_MAX_PAYLOAD = (255 - EXTRA_ROW_INFO_HDR_BYTES);

	// Events are without checksum though its generator
	public static final int BINLOG_CHECKSUM_ALG_OFF = 0;
	// is checksum-capable New Master (NM).
	// CRC32 of zlib algorithm.
	public static final int BINLOG_CHECKSUM_ALG_CRC32 = 1;
	// the cut line: valid alg range is [1, 0x7f].
	public static final int BINLOG_CHECKSUM_ALG_ENUM_END = 2;
	// special value to tag undetermined yet checksum
	public static final int BINLOG_CHECKSUM_ALG_UNDEF = 255;
	// or events from checksum-unaware servers

	public static final int CHECKSUM_CRC32_SIGNATURE_LEN = 4;
	public static final int BINLOG_CHECKSUM_ALG_DESC_LEN = 1;
	/**
	 * defined statically while there is just one alg implemented
	 */
	public static final int BINLOG_CHECKSUM_LEN = CHECKSUM_CRC32_SIGNATURE_LEN;

	/* MySQL or old MariaDB slave with no announced capability. */
	public static final int MARIA_SLAVE_CAPABILITY_UNKNOWN = 0;

	/* MariaDB >= 5.3, which understands ANNOTATE_ROWS_EVENT. */
	public static final int MARIA_SLAVE_CAPABILITY_ANNOTATE = 1;
	/*
	 * MariaDB >= 5.5. This version has the capability to tolerate events
	 * omitted from the binlog stream without breaking replication (MySQL slaves
	 * fail because they mis-compute the offsets into the master's binlog).
	 */
	public static final int MARIA_SLAVE_CAPABILITY_TOLERATE_HOLES = 2;
	/* MariaDB >= 10.0, which knows about binlog_checkpoint_log_event. */
	public static final int MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT = 3;
	/* MariaDB >= 10.0.1, which knows about global transaction id events. */
	public static final int MARIA_SLAVE_CAPABILITY_GTID = 4;

	/* Our capability. */
	public static final int MARIA_SLAVE_CAPABILITY_MINE = MARIA_SLAVE_CAPABILITY_GTID;

	/**
	 * For an event, 'e', carrying a type code, that a slave, 's', does not
	 * recognize, 's' will check 'e' for LOG_EVENT_IGNORABLE_F, and if the flag
	 * is set, then 'e' is ignored. Otherwise, 's' acknowledges that it has
	 * found an unknown event in the relay log.
	 */
	public static final int LOG_EVENT_IGNORABLE_F = 0x80;

	/** enum_field_types */
	public static final int MYSQL_TYPE_DECIMAL = 0;
	public static final int MYSQL_TYPE_TINY = 1;
	public static final int MYSQL_TYPE_SHORT = 2;
	public static final int MYSQL_TYPE_LONG = 3;
	public static final int MYSQL_TYPE_FLOAT = 4;
	public static final int MYSQL_TYPE_DOUBLE = 5;
	public static final int MYSQL_TYPE_NULL = 6;
	public static final int MYSQL_TYPE_TIMESTAMP = 7;
	public static final int MYSQL_TYPE_LONGLONG = 8;
	public static final int MYSQL_TYPE_INT24 = 9;
	public static final int MYSQL_TYPE_DATE = 10;
	public static final int MYSQL_TYPE_TIME = 11;
	public static final int MYSQL_TYPE_DATETIME = 12;
	public static final int MYSQL_TYPE_YEAR = 13;
	public static final int MYSQL_TYPE_NEWDATE = 14;
	public static final int MYSQL_TYPE_VARCHAR = 15;
	public static final int MYSQL_TYPE_BIT = 16;
	public static final int MYSQL_TYPE_TIMESTAMP2 = 17;
	public static final int MYSQL_TYPE_DATETIME2 = 18;
	public static final int MYSQL_TYPE_TIME2 = 19;
	public static final int MYSQL_TYPE_JSON = 245;
	public static final int MYSQL_TYPE_NEWDECIMAL = 246;
	public static final int MYSQL_TYPE_ENUM = 247;
	public static final int MYSQL_TYPE_SET = 248;
	public static final int MYSQL_TYPE_TINY_BLOB = 249;
	public static final int MYSQL_TYPE_MEDIUM_BLOB = 250;
	public static final int MYSQL_TYPE_LONG_BLOB = 251;
	public static final int MYSQL_TYPE_BLOB = 252;
	public static final int MYSQL_TYPE_VAR_STRING = 253;
	public static final int MYSQL_TYPE_STRING = 254;
	public static final int MYSQL_TYPE_GEOMETRY = 255;

	public static String getTypeName(final int type) {
		switch (type) {
		case START_EVENT_V3:
			return "Start_v3";
		case STOP_EVENT:
			return "Stop";
		case QUERY_EVENT:
			return "Query";
		case ROTATE_EVENT:
			return "Rotate";
		case INTVAR_EVENT:
			return "Intvar";
		case LOAD_EVENT:
			return "Load";
		case NEW_LOAD_EVENT:
			return "New_load";
		case SLAVE_EVENT:
			return "Slave";
		case CREATE_FILE_EVENT:
			return "Create_file";
		case APPEND_BLOCK_EVENT:
			return "Append_block";
		case DELETE_FILE_EVENT:
			return "Delete_file";
		case EXEC_LOAD_EVENT:
			return "Exec_load";
		case RAND_EVENT:
			return "RAND";
		case XID_EVENT:
			return "Xid";
		case USER_VAR_EVENT:
			return "User var";
		case FORMAT_DESCRIPTION_EVENT:
			return "Format_desc";
		case TABLE_MAP_EVENT:
			return "Table_map";
		case PRE_GA_WRITE_ROWS_EVENT:
			return "Write_rows_event_old";
		case PRE_GA_UPDATE_ROWS_EVENT:
			return "Update_rows_event_old";
		case PRE_GA_DELETE_ROWS_EVENT:
			return "Delete_rows_event_old";
		case WRITE_ROWS_EVENT_V1:
			return "Write_rows_v1";
		case UPDATE_ROWS_EVENT_V1:
			return "Update_rows_v1";
		case DELETE_ROWS_EVENT_V1:
			return "Delete_rows_v1";
		case BEGIN_LOAD_QUERY_EVENT:
			return "Begin_load_query";
		case EXECUTE_LOAD_QUERY_EVENT:
			return "Execute_load_query";
		case INCIDENT_EVENT:
			return "Incident";
		case HEARTBEAT_LOG_EVENT:
			return "Heartbeat";
		case IGNORABLE_LOG_EVENT:
			return "Ignorable";
		case ROWS_QUERY_LOG_EVENT:
			return "Rows_query";
		case WRITE_ROWS_EVENT:
			return "Write_rows";
		case UPDATE_ROWS_EVENT:
			return "Update_rows";
		case DELETE_ROWS_EVENT:
			return "Delete_rows";
		case GTID_LOG_EVENT:
			return "Gtid";
		case ANONYMOUS_GTID_LOG_EVENT:
			return "Anonymous_Gtid";
		case PREVIOUS_GTIDS_LOG_EVENT:
			return "Previous_gtids";
		default:
			return "Unknown"; /* impossible */
		}
	}

	protected static final Logger logger = LoggerFactory.getLogger(LogEvent.class);

	protected final LogHeader header;

	protected LogEvent(LogHeader header) {
		this.header = header;
	}

	/**
	 * Return event header.
	 */
	public final LogHeader getHeader() {
		return header;
	}

	/**
	 * The total size of this event, in bytes. In other words, this is the sum
	 * of the sizes of Common-Header, Post-Header, and Body.
	 */
	public final int getEventLen() {
		return header.getEventLen();
	}

	/**
	 * Server ID of the server that created the event.
	 */
	public final long getServerId() {
		return header.getServerId();
	}

	/**
	 * The position of the next event in the master binary log, in bytes from
	 * the beginning of the file. In a binlog that is not a relay log, this is
	 * just the position of the next event, in bytes from the beginning of the
	 * file. In a relay log, this is the position of the next event in the
	 * master's binlog.
	 */
	public final long getLogPos() {
		return header.getLogPos();
	}

	/**
	 * The time when the query started, in seconds since 1970.
	 */
	public final long getWhen() {
		return header.getWhen();
	}

}
